#include "tour.h"

char	lo_ip[INET_ADDRSTRLEN];				/* eth0 ip address of this node */
char	tour_src_ip[INET_ADDRSTRLEN];			/* tour source node IP address */
int	curr_tour_id = 0;				/* current tour id the node belongs to */
int     ping_dlen = 56;   				/* data that goes with ICMP echo request */
int	ll_sockfd;					/* link layer sock fd */
char	ping_sendbuf[ETH_FRAME_LEN + ETH_FCS_LEN];	
pid_t	pid;						
int	nsent = 0;					
int	ping_reply_cnt = 0;

int tour_step(int sockfd, struct tourhdr *thdr, char ip_tour_list[][INET_ADDRSTRLEN])
{
	struct iphdr 		ipheader;
	struct in_addr		srcIP, dstIP;
	struct sockaddr_in	dst_addr;
	char			buffer[IP_PACKET_LEN];
	int			rv;

	bzero(&ipheader, sizeof(ipheader));
	bzero(&srcIP, sizeof(srcIP));
	bzero(&dstIP, sizeof(dstIP));
	bzero(&dst_addr, sizeof(dst_addr));

	inet_pton(AF_INET, thdr->tour_src_ip, &srcIP);
	inet_pton(AF_INET, ip_tour_list[thdr->tour_nxt_idx], &dstIP);

	ipheader.version = 4;
	ipheader.ihl = 5;
	ipheader.tos = 0;
	ipheader.tot_len = sizeof(struct iphdr) + sizeof(struct tourhdr) + thdr->tour_len*INET_ADDRSTRLEN;
	ipheader.id = IPHDR_ID_GRP23;
	ipheader.frag_off = 0;
	ipheader.ttl = 1;
	ipheader.protocol = IPPROTO_GRP23;
	ipheader.saddr = srcIP.s_addr;
	ipheader.daddr = dstIP.s_addr;
	
	memcpy(buffer, &ipheader, sizeof(ipheader));
	memcpy(buffer + sizeof(ipheader), thdr, sizeof(struct tourhdr));
	memcpy(buffer + sizeof(ipheader) + sizeof(struct tourhdr), ip_tour_list[0], thdr->tour_len * INET_ADDRSTRLEN);

	dst_addr.sin_family = AF_INET;
	memcpy(&dst_addr.sin_addr, &dstIP, sizeof(struct in_addr));

	rv = sendto(sockfd, buffer, ipheader.tot_len, 0, (SA*) &dst_addr, sizeof(dst_addr));

	if(rv < 0)
	{
		perror("Error walking through the tour: ");
		printf("Terminating the tour application.\n");
		exit(0);
	}
	
	return rv;
}

int get_iplist(int ipc, char *ipv[], char ip_tour_list[][INET_ADDRSTRLEN])
{
	int	i;
	char	*lo_hostname = lookup_hostbyip(lo_ip);

	for(i = 1; i < ipc; i++)
	{
		if(strncmp(ipv[i], lo_hostname, strlen(lo_hostname)) == 0)
		{
			printf("Source node %s cannot be part of the tour. Terminating the tour application.\n", lo_hostname);
			exit(0);
		}
		if(lookup_hostbyname(ipv[i], ip_tour_list[i - 1]) < 0)
		{
			printf("Resolution of %s failed. Terminating the tour application.\n", ipv[i]);
			exit(0);
		}
	}

	return 0;
}

int set_iplist(char *buffer, char ip_tour_list[][INET_ADDRSTRLEN], int tour_len)
{
	int	i;
	
	for(i = 0; i < tour_len; i++)
	{
		memcpy(ip_tour_list[i], buffer, INET_ADDRSTRLEN);
		buffer += INET_ADDRSTRLEN;
	}

	return 0;
}

int ping_tour_src()
{
	//disable the alarm
	alarm(0);
	
	int 			len;
	struct icmp 		*icmp;
	struct iphdr 		*ipheader;
	struct ethhdr		*ethheader;
	struct in_addr		srcIP, dstIP;
	struct sockaddr_ll	sal;
	int			rv;

	bzero(&sal, sizeof(sal));
	bzero(ping_sendbuf, sizeof(ping_sendbuf));
	/* form the icmp header */
	ethheader = (struct ethhdr *) (ping_sendbuf);
	ipheader = (struct iphdr *) (ping_sendbuf + sizeof(struct ethhdr));
	icmp = (struct icmp *) (ping_sendbuf + sizeof(struct iphdr) + sizeof(struct ethhdr));
	icmp->icmp_type = ICMP_ECHO;
	icmp->icmp_code = 0;
	icmp->icmp_id = pid;
	icmp->icmp_seq = nsent++;
	memset(icmp->icmp_data, 0xa5, ping_dlen); /* fill something */

	Gettimeofday((struct timeval *) icmp->icmp_data, NULL);
	len = 8 + ping_dlen; /* checksum ICMP header and data */
	icmp->icmp_cksum = 0;
	icmp->icmp_cksum = in_cksum((u_short *) icmp, len);

	/* form the ip header */

	bzero(&srcIP, sizeof(srcIP));
	bzero(&dstIP, sizeof(dstIP));

	inet_pton(AF_INET, lo_ip, &srcIP);
	inet_pton(AF_INET, tour_src_ip, &dstIP);
	
	arp_hwaddr ahw;
	memset(&ahw, 0, sizeof(ahw));
	
	/* AREQ */
	rv = areq(&dstIP, sizeof(struct sockaddr_in), &ahw);  
	if (rv < 0) {
		printf("AREQ failed. Terminating the tour application.\n");
		exit(0);
	}

	ipheader->version = 4;
	ipheader->ihl = 5;
	ipheader->tos = 0;
	ipheader->tot_len = htons(sizeof(struct iphdr) + len);
	ipheader->id = htons(pid);
	ipheader->frag_off = htons(0);
	ipheader->ttl = 1;
	ipheader->protocol = IPPROTO_ICMP;
	ipheader->saddr = srcIP.s_addr;
	ipheader->daddr = dstIP.s_addr;
	ipheader->check = 0;
	ipheader->check = in_cksum((ushort *) ipheader, ntohs(ipheader->tot_len));
	
	/* take the source and dest eth addr from areq (ETHALEN size)*/
	memcpy(ethheader->h_source, hweth0->if_haddr, ETH_ALEN);
	memcpy(ethheader->h_dest, ahw.hwaddr, ETH_ALEN);

	ethheader->h_proto = htons(ETH_P_IP);

	/* get this info from hwaddr structure */
	sal.sll_family = AF_PACKET;
	sal.sll_protocol = htons(ETH_P_IP);
	sal.sll_ifindex = ahw.ifindex;
	sal.sll_hatype = ARPHRD_ETHER;
	sal.sll_pkttype = PACKET_OTHERHOST;
	sal.sll_halen = ETH_ALEN;
	memcpy(&sal.sll_addr, ahw.hwaddr, ETH_ALEN); // get it from h_dest
	
	rv = sendto(ll_sockfd, ping_sendbuf, ETH_FRAME_LEN, 0, (SA *) &sal, sizeof(sal));
	if(rv <= 0)
		perror("Couldn't send ping request.");
	return rv;

}

void sig_alrm(int signo)
{
	int rv = ping_tour_src();
	if(rv < 0)
		alarm(0);
	else
		alarm(1);
	return;
}

int main(int argc, char *argv[])
{
	/* declarations */
	int			rt_sockfd, pg_sockfd, mc_sockfd;	//routing, ping, multicast sockets
	char			ip_tour_list[100][INET_ADDRSTRLEN];		//tour ip list XXX: make it **pointer
	struct tourhdr		tour_hdr;					//tour details
	char			buffer[IP_PACKET_LEN];
	int			maxfd = 0, on = 1, rv, retraversal = 0;
	fd_set			rset;
	struct sockaddr_in	mc_addr;
	struct mcmsg		mc_msg;
	struct timeval		timeout_tv;
	int			tour_ended = 0, wait_on_mc = 0;
	time_t 			ticks;

	/* wipe the structs clean */
	bzero(&tour_hdr, sizeof(tour_hdr));
	bzero(&mc_addr, sizeof(mc_addr));
	bzero(&mc_msg, sizeof(mc_msg));
	bzero(&timeout_tv, sizeof(timeout_tv));

	/* initialize required variables */
	lookup_hostbyname(getlohostname(), lo_ip);
	mc_addr.sin_family = AF_INET;
	pid = getpid() & 0xffff; /* ICMP ID field is 16 bits */
	
	populate_eth0_hw(false);
	
	Signal(SIGALRM, sig_alrm);

	/* create the sockets */
	rt_sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_GRP23);
	rv = setsockopt(rt_sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
	if(rv < 0)	printf("Cannot set HDRINCL on the rt socket.\n");

	pg_sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
	//rv = setsockopt(rt_sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
	//if(rv < 0)	printf("Cannot set HDRINCL on the rt socket.\n");

	ll_sockfd = socket(PF_PACKET, SOCK_RAW, ETH_P_IP);
	//rv = setsockopt(rt_sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
	//if(rv < 0)	printf("Cannot set HDRINCL on the rt socket.\n");
	
	mc_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	on = 1;
	rv = setsockopt(mc_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	if(rv < 0)	printf("Cannot set REUSEADDR on the mc socket\n");

	if (argc > 100) {
		printf("Too many participants in the tour, terminating the tour.\n");
		exit(0);
	}
	/* Process the input vms for tour, if any, through the
	 * command line parameter; should not include self */
	if(argc > 1)
	{
		get_iplist(argc, argv, ip_tour_list);
		/* only source uses the defined MACROS for multicast IP:Port */
		inet_pton(AF_INET, MC_IP, &mc_addr.sin_addr);
		mc_addr.sin_port = MC_PORT;
		bind(mc_sockfd, (SA*) &mc_addr, sizeof(mc_addr));

		/* tour starts here; so set the source and being the tour */
		tour_hdr.tour_id = rand();
		strncpy(tour_hdr.tour_src_ip, lo_ip, INET_ADDRSTRLEN);
		tour_hdr.tour_len = argc - 1;
		tour_hdr.tour_nxt_idx = 0;

		/* assign the multicast ip, port and do the src registration */
		strncpy(tour_hdr.multicast_ip, MC_IP, INET_ADDRSTRLEN);
		tour_hdr.multicast_port = MC_PORT;

		curr_tour_id = tour_hdr.tour_id;
		
		/* join the multicast group */
		Mcast_join(mc_sockfd, (SA *) &mc_addr, sizeof(mc_addr), NULL, 0);
		Mcast_set_loop(mc_sockfd, 1);

		//printf("Starting the tour at %s\n", getlohostname());
		tour_step(rt_sockfd, &tour_hdr, ip_tour_list);
	}

	for(;;)
	{
		FD_ZERO(&rset);
		bzero(buffer, sizeof(buffer));
		
		/* listen on sockets as */
		if(!wait_on_mc)
		{
			FD_SET(rt_sockfd, &rset);
			FD_SET(mc_sockfd, &rset);
			FD_SET(pg_sockfd, &rset);
			maxfd = max(pg_sockfd, max(rt_sockfd, mc_sockfd));
		}
		else
		{
			FD_SET(mc_sockfd, &rset);
			maxfd = mc_sockfd;
		}
		select(maxfd + 1, &rset, NULL, NULL, &timeout_tv);

		//printf("Select Unlocked\n");
		if(FD_ISSET(rt_sockfd, &rset))
		{
			struct tourhdr 		*thdr;
			struct sockaddr_in	from_addr;
			struct iphdr		*ipheader;
			socklen_t		from_len = sizeof(from_addr);

			/* receive the tour packet */
			rv = recvfrom(rt_sockfd, buffer, IP_PACKET_LEN, 0, (SA *) &from_addr, &from_len);

			if(rv <= 0)	
			{
				if(errno != EINTR)
					perror("Error receiving tour packet.");
				//else: signal alarm was fired
				continue;
			}

			ipheader = (struct iphdr *) buffer;
			if(ipheader->id != IPHDR_ID_GRP23)
			{
				printf("Received IPHDR_ID : %d is not expected. Ignoring the recvfrom\n", 
					ipheader->id);
			}
			
			thdr = (struct tourhdr *) (buffer + sizeof(struct iphdr));

			/* extract the ip tour list */
			set_iplist(buffer + sizeof(struct iphdr) + sizeof(struct tourhdr), ip_tour_list, thdr->tour_len);
			
			ticks = time(NULL);
			if(thdr->tour_nxt_idx == 0)
			{
				printf("<%.24s> Received source routing packet from %s\n", 
					ctime(&ticks), lookup_hostbyip(thdr->tour_src_ip));
			}
			else
			{
				printf("<%.24s> Received source routing packet from %s\n", 
					ctime(&ticks), lookup_hostbyip(ip_tour_list[thdr->tour_nxt_idx - 1]));
			}
			
			/* if not the end of traversal */
			if(thdr->tour_len - ++(thdr->tour_nxt_idx) > 0)
			{
				/* moving to the node to be toured next */
				if(tour_step(rt_sockfd, thdr, ip_tour_list) < 0)
				{
					printf("Error walking on the tour. Terminating\n");
					exit(0);
				}

			}
			/* end of traversal */
			else
			{
				/* wait for few pings to go through */
				tour_ended = 1;
			}
	
			/* if this is the first traversal in the tour */
			if(thdr->tour_id != curr_tour_id)
			{
				/* update the current tour's id */
				curr_tour_id = thdr->tour_id;
				//XXX:start pinging the source node
				memcpy(tour_src_ip, thdr->tour_src_ip, INET_ADDRSTRLEN);
				printf("PING %s (%s): %d data bytes\n", lookup_hostbyip(tour_src_ip), tour_src_ip, ping_dlen);
				sig_alrm(SIGALRM);
				//ping_tour_src();
				/* bind to the received multicast and join the group*/
				inet_pton(AF_INET, thdr->multicast_ip, &mc_addr.sin_addr);
				mc_addr.sin_port = thdr->multicast_port;
				bind(mc_sockfd, (SA*) &mc_addr, sizeof(mc_addr));
				Mcast_join(mc_sockfd, (SA*) &mc_addr, sizeof(mc_addr), NULL, 0);
				Mcast_set_loop(mc_sockfd, 1);
			}
			else
			{
				/* the node already started pinging and joined the group
				 * in the first traversal. So first leave it alone! */
				 //printf("\nThe node already started pinging and joined the group in the first traversal!\n\n");
				 retraversal = 1;
			}

		}
		if(FD_ISSET(pg_sockfd, &rset))
		{
			//receive the ping packet and display
			char  		  	recvbuf[PING_BUFSIZE];
			int     		len, hlenl, icmplen, from_len;
			double  		rtt;
			struct ip	 	*ip;
			struct icmp 		*icmp;
			struct timeval 		*tvsend, tvrecv;
			struct sockaddr_in	from_addr;
			char			from_ip[INET_ADDRSTRLEN];

			from_len = sizeof(from_addr);
			len = recvfrom(pg_sockfd, recvbuf, PING_BUFSIZE, 0, (SA *) &from_addr, &from_len);
			if(len <= 0)
			{
				if(errno != EINTR)
					perror("couldn't receive the ping response");
				//else: signal alarm was fired
				continue;
			}
			Gettimeofday (&tvrecv, NULL);
			inet_ntop(AF_INET, &from_addr.sin_addr, from_ip, INET_ADDRSTRLEN);

			ip = (struct ip *) recvbuf;
			hlenl = ip->ip_hl << 2;
			if(ip->ip_p != IPPROTO_ICMP) //not an ICMP packet
			{
				//printf("not an ICMP packet\n");
				continue;
			}
			icmp = (struct icmp *) (recvbuf + hlenl);
			if((icmplen = len - hlenl) < 8) //malformed packet
			{
				//printf("malformed packet\n");
				continue;
			}
			
			if(icmp->icmp_type == ICMP_ECHOREPLY)
			{
				if(icmp->icmp_id != pid) //not a response to our ECHO_REQUEST
				{
					//printf("not our ECHO response\n");
					continue;
				}
				if(icmplen < 16) //not enough data to use
				{
					//printf("not enought data to use\n");
					continue;
				}

				tvsend = (struct timeval *) icmp->icmp_data;
				tv_sub(&tvrecv, tvsend);
				rtt = tvrecv.tv_sec * 1000.0 + tvrecv.tv_usec / 1000.0;

				printf ("%d bytes from %s (%s): seq=%u, ttl=%d, rtt=%.3f ms\n",
					icmplen, lookup_hostbyip(from_ip), from_ip, icmp->icmp_seq, ip->ip_ttl, rtt);

				if(++ping_reply_cnt > 5 && tour_ended)
				{
					/*if(retraversal)
					{
						sleep(5);
					}
					else
					{*/
					/*}*/
					/* end the ping */
					tour_ended = 0;
					/* send the multicast saying that the tour ended */
					snprintf(mc_msg.msg, sizeof(mc_msg.msg),
							"<<<<< This is node %s. Tour has ended. Group members please identify yourselves. >>>>>\0", lookup_hostbyip(lo_ip));
					mc_msg.msg_type = MC_MSG_EOT;
					printf("Node %s. Sending: %s\n", lookup_hostbyip(lo_ip), mc_msg.msg);
					
					rv = sendto(mc_sockfd, &mc_msg, sizeof(mc_msg), 0, (SA*) &mc_addr, sizeof(mc_addr));
					if(rv <= 0)
					{
						perror("perror: ");
						printf("sento for mc failed. rv = %d\n", rv);
					}
					
				}
			}
		}
		if(FD_ISSET(mc_sockfd, &rset))
		{
			struct sockaddr_in	from_addr;
                        socklen_t		from_len = sizeof(from_addr);
			struct mcmsg		mc_msg;
			char			from_ip[INET_ADDRSTRLEN];

                        rv = recvfrom(mc_sockfd, &mc_msg, sizeof(mc_msg), 0, (SA *) &from_addr, &from_len);

			inet_ntop(AF_INET, &from_addr.sin_addr, from_ip, sizeof(from_addr.sin_addr));
			
			printf("Node %s. Received: %s\n", lookup_hostbyip(lo_ip), mc_msg.msg);

			/* end of tour message and the current node is not the tour source */
			if(mc_msg.msg_type == MC_MSG_EOT)
			{
				if(strncmp(lo_ip, tour_src_ip, INET_ADDRSTRLEN) != 0)
				{
					/* end the ping */
					alarm(0);
				}
				/* send identification message */
				struct mcmsg idn_msg;
				snprintf(idn_msg.msg, sizeof(idn_msg.msg),
					"<<<<< Node %s. I am a member of the group. >>>>>\0", lookup_hostbyip(lo_ip));
				printf("Node %s. Sending: %s\n", lookup_hostbyip(lo_ip), idn_msg.msg);
				idn_msg.msg_type = MC_MSG_IDN;
				rv = sendto(mc_sockfd, &idn_msg, sizeof(idn_msg), 0, (SA *) &mc_addr, sizeof(mc_addr));
				if(rv <= 0)
				{
					perror("perror: ");
					printf("sento for mc failed. rv = %d\n", rv);
				}	
				
			}
			else if(mc_msg.msg_type == MC_MSG_IDN)
			{
				timeout_tv.tv_sec = 5;
				wait_on_mc = 1;
				continue;
			}
			fflush(stdout);
		}
		/* timeout waiting on the multicast messages. graceful exit */
		if(wait_on_mc)
		{
			/* time out. gracefully exit tour */
			printf("Gracefully exiting the tour application.\n");
			exit(0);
		}
	}
}

